using System;
using Microsoft.SPOT;
using System.Collections;

namespace N2Plus_GPS
{
    /// <summary>
    /// Class used to verify GPS location
    /// </summary>
    public static class Locator
    {
        #region Private Variables
        private static double _PI = 3.14159265;
        private static double _TWOPI = 2 * _PI;
        #endregion

        #region Methods
        #region Public
        /// <summary>
        /// Will determine if the current gps coordinate is within a specified area
        /// </summary>
        /// <param name="pCurrentPoint">Your current gps point</param>
        /// <param name="pSector">The sector in question</param>
        /// <returns>True if in area, false otherwise</returns>
        public static bool IsInSector(GPSPoint pCurrentPoint, Sector pSector)
        {
            bool lvResult = false;


            try
            {
                int i = 0;
                double lvAngle=0;
                int n = pSector.GPSArea.Count;
                GPSPoint lvNewGPSPoint1 = new GPSPoint();
                GPSPoint lvNewGPSPoint2 = new GPSPoint();

                for (i=0;i<n;i++) {
                    GPSPoint lvGPSPoint1 = (GPSPoint)pSector.GPSArea[i];
                    GPSPoint lvGPSPoint2 = (GPSPoint)pSector.GPSArea[(i + 1) % n];


                    lvNewGPSPoint1.Latitude = lvGPSPoint1.Latitude - pCurrentPoint.Latitude;
                    lvNewGPSPoint1.Longitude = lvGPSPoint1.Longitude - pCurrentPoint.Longitude;
                    lvNewGPSPoint2.Latitude = lvGPSPoint2.Latitude - pCurrentPoint.Latitude;
                    lvNewGPSPoint2.Longitude = lvGPSPoint2.Longitude - pCurrentPoint.Longitude;
                    lvAngle += Angle2D(lvNewGPSPoint1,lvNewGPSPoint2);
                }

                if (System.Math.Abs(lvAngle) < _PI)
                    lvResult = false;
                else
                    lvResult = true;

            }
            catch
            {
                lvResult = false;
            }

            return lvResult;
        }

        /// <summary>
        /// Will determine if the current gps coordinate is within a specified area
        /// </summary>
        /// <param name="pCurrentPoint">Your current gps point</param>
        /// <param name="pSector">The sector in question</param>
        /// <returns>True if in area, false otherwise</returns>
        public static bool IsInSectorNew(GPSPoint pCurrentPoint, Sector pSector)
        {
            bool lvResult = false;

            if (WindingNumberAlgoritm(pCurrentPoint, pSector) > 0)
            {
                lvResult = true;
            }

            return lvResult;
        }
        /// <summary>
        /// Check to see if Point is valid
        /// </summary>
        /// <param name="pPoint">The gps point in question</param>
        /// <returns>True if valid else false for invalid coordinates</returns>
        public static bool IsValidGPSCoordinate(GPSPoint pPoint)
        {
            bool lvResult = false;

            if (pPoint.Latitude > -90 && pPoint.Latitude < 90 &&
                pPoint.Longitude > -180 && pPoint.Longitude < 180)
            {
                lvResult = true;
            }

            return lvResult;
        }
        #endregion

        #region Private
        private static double Angle2D(GPSPoint pGPSPoint1, GPSPoint pGPSPoint2)
        {
            double dtheta, theta1, theta2;

            theta1 = System.Math.Atan2(pGPSPoint1.Latitude, pGPSPoint1.Longitude);
            theta2 = System.Math.Atan2(pGPSPoint2.Latitude, pGPSPoint2.Longitude);
            dtheta = theta2 - theta1;
            while (dtheta > _PI)
                dtheta -= _TWOPI;
            while (dtheta < -_PI)
                dtheta += _TWOPI;

            return (dtheta);
        }

        /// <summary>
        /// Tests if a point is Left|On|Right of an infinite line
        /// </summary>
        /// <param name="pCurrentPoint"></param>
        /// <param name="pPoint1"></param>
        /// <param name="pPoint2"></param>
        /// <returns>
        /// >0 for P2 left of the line through P0 and P1
        //  =0 for P2  on the line
        //  <0 for P2  right of the line
        /// </returns>
        private static double IsLeft(GPSPoint pPoint1, GPSPoint pPoint2, GPSPoint pCurrentPoint)
        {
            double lvResult = 0;

            lvResult = ((pPoint2.Longitude - pPoint1.Longitude) * (pCurrentPoint.Latitude - pPoint1.Latitude)
                        - (pCurrentPoint.Longitude - pPoint1.Longitude) * (pPoint2.Latitude - pPoint1.Latitude));

            return lvResult;
        }
        /// <summary>
        /// Winding number test for a point in a polygon
        /// </summary>
        /// <param name="pCurrentPoint"></param>
        /// <param name="pSector"></param>
        /// <returns>
        /// =0 if outside sector
        /// >0 if inside sector
        /// </returns>
        private static int WindingNumberAlgoritm(GPSPoint pCurrentPoint, Sector pSector)
        {
            int lvResult = 0;

            //Cycle through all the sector points
            for (int i = 0; i < pSector.GPSArea.Count; i++)
            {
                GPSPoint lvGPSPoint1 = (GPSPoint)pSector.GPSArea[i];
                GPSPoint lvGPSPoint2 = null;
                if (i == pSector.GPSArea.Count - 1)
                {
                    //Link to first item
                    lvGPSPoint2 = (GPSPoint)pSector.GPSArea[0];
                }
                else
                {
                    lvGPSPoint2 = (GPSPoint)pSector.GPSArea[i+1];
                }

                if (lvGPSPoint1.Latitude <= pCurrentPoint.Latitude)
                {
                    if (lvGPSPoint2.Latitude > pCurrentPoint.Latitude)
                    {
                        if (IsLeft(lvGPSPoint1, lvGPSPoint2, pCurrentPoint) > 0)
                        {
                            ++lvResult;
                        }
                    }
                }
                else
                {
                    if (lvGPSPoint2.Latitude <= pCurrentPoint.Latitude)
                    {
                        if (IsLeft(lvGPSPoint1, lvGPSPoint2, pCurrentPoint) < 0)
                        {
                            --lvResult;
                        }
                    }
                }
            }


            return lvResult;
        }


        #endregion
        #endregion
    }

    /// <summary>
    /// Class to store GPS coordinates Longitude and Latitude
    /// </summary>
    public class GPSPoint
    {

        #region Constructor
        /// <summary>
        /// Default constructor
        /// </summary>
        public GPSPoint()
        {

        }

        /// <summary>
        /// Overloaded constructor
        /// </summary>
        /// <param name="pLatitude">Latitude of gps point</param>
        /// <param name="pLongitude">Longitude of gps point</param>
        public GPSPoint(double pLatitude, double pLongitude)
        {
            Latitude = pLatitude;
            Longitude = pLongitude;
        }
        #endregion

        #region Properties
        /// <summary>
        /// The GPS Longitude value
        /// </summary>
        public double Longitude
        {
            get;
            set;
        }

        /// <summary>
        /// The GPS Latitude value
        /// </summary>
        public double Latitude
        {
            get;
            set;
        }
        #endregion
    }
}
